package com.dly.blog.utile;

import com.dly.blog.content.RedisContent;
import lombok.AllArgsConstructor;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author: DaiLiYu
 * @Date: 2023/7/19 14:17
 */
@Component
@AllArgsConstructor
@Order(-1)
public class RedisUtils {

    private final RedisTemplate<Object, Object> redisTemplate;

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 带有效期的缓存
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
       return (T) redisTemplate.opsForValue().get(key);
    }


    /**
     * 添加set
     * @param key
     * @param value
     */
    public Long addSet(String key, Object value){
        return redisTemplate.opsForSet().add(key, value);
    }

    /**
     * 删除set的值
     * @param key
     * @param value
     */
    public void removeSet(String key, Object value){
        redisTemplate.opsForSet().remove(key,value);
    }

    public void addZSet(String key, String zKey){
        redisTemplate.opsForZSet().incrementScore(key, zKey, 1);
    }

    public void removeZSet(String key, String zKey){
        redisTemplate.opsForZSet().incrementScore(key, zKey, -1);
    }

    public  <T> Set<T> getZSet(String key, long start, long end){
        return (Set<T>) redisTemplate.opsForZSet().range(key, start, end);
    }

    public void removeKey(String key){
        redisTemplate.delete(key);
    }

    public <T> Set<T> getSet(String key){
        return (Set<T>) redisTemplate.opsForSet().members(key);
    }

    /**
     * 使用bitMap 实现签到
     * @param key key需要带上年月
     * @param offerSet 偏移量 0 - 31 直接用 1-31位作为标记
     * @param isChick 是否签到
     */
    public void setBitMap(String key, long offerSet, boolean isChick){
        redisTemplate.opsForValue().setBit(key, offerSet, isChick);
    }

    public Boolean getBitMap(String key, long offerSet){
       return redisTemplate.opsForValue().getBit(key, offerSet);
    }

    public Long getAllBitMap(String key){

        List<Long> longs = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(31))
                .valueAt(1));
        if(!Objects.isNull(longs)){
            return longs.get(0);
        }
        return null;
    }

    /**
     * 连续签到天数
     * @param key key
     * @param monthCountDay 这个月天数
     * @param offerSet 几号
     */
    public Integer countSign(String key, Integer monthCountDay, Integer offerSet){
        List<Long> longs = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(monthCountDay))
                .valueAt(1));
        long v = Objects.isNull(longs) ? 0 : longs.get(0);
        int signCount = 0;
        v = v >> monthCountDay - offerSet;
        // 位移计算连续签到次数
        for (int i = offerSet; i > 0; i--) {// i 表示位移操作次数
            // 右移再左移，如果等于自己说明最低位是 0，表示未签到
            if (v >> 1 << 1 == v) {
                // 用户可能当前还未签到，所以要排除是否是当天的可能性
                // 低位 0 且非当天说明连续签到中断了
                if (i != offerSet) break;
            } else {
                // 右移再左移，如果不等于自己说明最低位是 1，表示签到
                signCount++;
            }
            // 右移一位并重新赋值，相当于把最低位丢弃一位然后重新计算
            v >>= 1;
        }
        return signCount;
    }


    public List<Long> getCountLua(String... contentIds){
        DefaultRedisScript<List> redis = new DefaultRedisScript<>();
        redis.setResultType(List.class);
        redis.setScriptText(
                """
                local maps = {}
                for i = 1, #KEYS do
                    local total = 0
                    for j = 1, #ARGV do
                        local score = redis.call('ZSCORE', KEYS[i], ARGV[j])
                        if score then
                            total = total + tonumber(score)
                        end
                    end
                    maps[i] = total
                end
                return maps
                """
        );
        return (List<Long>) redisTemplate.execute(redis, List.of("favorite_sort", "read_sort", "support_sort"), contentIds);
    }
}
